/////////////////////////////////////////////////////////////////////////////
// Original code from libhdfs3. Copyright (c) 2013 - 2014, Pivotal Inc.
// All rights reserved. Author: Zhanwei Wang
/////////////////////////////////////////////////////////////////////////////
//  Modifications by Kumo Inc.
// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//



#include <kmhdfs/common/exception.h>
#include <kmhdfs/common/exception_internal.h>
#include <kmhdfs/common/xml_config.h>
#include <kmhdfs/common/hash.h>

#include <cassert>
#include <cerrno>
#include <fstream>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <limits>
#include <cstring>
#include <unistd.h>

using namespace Hdfs::Internal;

namespace Hdfs {
    typedef std::map<std::string, std::string>::const_iterator Iterator;
    typedef std::map<std::string, std::string> Map;

    static int32_t StrToInt32(const char *str) {
        long retval;
        char *end = NULL;
        errno = 0;
        retval = strtol(str, &end, 0);

        if (EINVAL == errno || 0 != *end) {
            THROW(HdfsBadNumFoumat, "Invalid int32_t type: %s", str);
        }

        if (ERANGE == errno || retval > std::numeric_limits<int32_t>::max()
            || retval < std::numeric_limits<int32_t>::min()) {
            THROW(HdfsBadNumFoumat, "Underflow/Overflow int32_t type: %s", str);
        }

        return retval;
    }

    static int64_t StrToInt64(const char *str) {
        long long retval;
        char *end = NULL;
        errno = 0;
        retval = strtoll(str, &end, 0);

        if (EINVAL == errno || 0 != *end) {
            THROW(HdfsBadNumFoumat, "Invalid int64_t type: %s", str);
        }

        if (ERANGE == errno || retval > std::numeric_limits<int64_t>::max()
            || retval < std::numeric_limits<int64_t>::min()) {
            THROW(HdfsBadNumFoumat, "Underflow/Overflow int64_t type: %s", str);
        }

        return retval;
    }

    static bool StrToBool(const char *str) {
        bool retval = false;

        if (!strcasecmp(str, "true") || !strcmp(str, "1")) {
            retval = true;
        } else if (!strcasecmp(str, "false") || !strcmp(str, "0")) {
            retval = false;
        } else {
            THROW(HdfsBadBoolFoumat, "Invalid bool type: %s", str);
        }

        return retval;
    }

    static double StrToDouble(const char *str) {
        double retval;
        char *end = NULL;
        errno = 0;
        retval = strtod(str, &end);

        if (EINVAL == errno || 0 != *end) {
            THROW(HdfsBadNumFoumat, "Invalid double type: %s", str);
        }

        if (ERANGE == errno || retval > std::numeric_limits<double>::max()
            || retval < std::numeric_limits<double>::min()) {
            THROW(HdfsBadNumFoumat, "Underflow/Overflow int64_t type: %s", str);
        }

        return retval;
    }

    static void readConfigItem(xmlNodePtr root, Map &kv, const char *path) {
        std::string key, value;
        xmlNodePtr curNode;
        bool hasname = false, hasvalue = false;

        for (curNode = root; NULL != curNode; curNode = curNode->next) {
            if (curNode->type != XML_ELEMENT_NODE) {
                continue;
            }

            if (!hasname && !strcmp((const char *) curNode->name, "name")) {
                if (NULL != curNode->children
                    && XML_TEXT_NODE == curNode->children->type) {
                    key = (const char *) curNode->children->content;
                    hasname = true;
                }
            } else if (!hasvalue
                       && !strcmp((const char *) curNode->name, "value")) {
                if (NULL != curNode->children
                    && XML_TEXT_NODE == curNode->children->type) {
                    value = (const char *) curNode->children->content;
                    hasvalue = true;
                }
            } else {
                continue;
            }
        }

        if (hasname && hasvalue) {
            kv[key] = value;
            return;
        } else if (hasname) {
            kv[key] = "";
            return;
        }

        THROW(HdfsBadConfigFoumat, "Config cannot parse configure file: \"%s\"",
              path);
    }

    static void readConfigItems(xmlDocPtr doc, Map &kv, const char *path) {
        xmlNodePtr root, curNode;
        root = xmlDocGetRootElement(doc);

        if (NULL == root || strcmp((const char *) root->name, "configuration")) {
            THROW(HdfsBadConfigFoumat, "Config cannot parse configure file: \"%s\"",
                  path);
        }

        /*
         * for each property
         */
        for (curNode = root->children; NULL != curNode; curNode = curNode->next) {
            if (curNode->type != XML_ELEMENT_NODE) {
                continue;
            }

            if (strcmp((const char *) curNode->name, "property")) {
                THROW(HdfsBadConfigFoumat,
                      "Config cannot parse configure file: \"%s\"", path);
            }

            readConfigItem(curNode->children, kv, path);
        }
    }

    Config::Config(const char *p) : path(p) {
        update(p);
    }

    void Config::update(const char *p) {
        xmlDocPtr doc; /* the resulting document tree */
        LIBXML_TEST_VERSION
        kv.clear();
        path = p;

        if (access(path.c_str(), R_OK)) {
            THROW(HdfsBadConfigFoumat, "Cannot read configure file: \"%s\", %s",
                  path.c_str(), GetSystemErrorInfo(errno));
        }

        /* parse the file */
        doc = xmlReadFile(path.c_str(), NULL, 0);

        try {
            /* check if parsing succeeded */
            if (doc == NULL) {
                THROW(HdfsBadConfigFoumat,
                      "Config cannot parse configure file: \"%s\"", path.c_str());
            } else {
                readConfigItems(doc, kv, path.c_str());
                /* free up the resulting document */
                xmlFreeDoc(doc);
            }
        } catch (...) {
            xmlFreeDoc(doc);
            throw;
        }
    }

    const char *Config::getString(const char *key) const {
        Iterator it = kv.find(key);

        if (kv.end() == it) {
            THROW(HdfsConfigNotFound, "Config key: %s not found", key);
        }

        return it->second.c_str();
    }

    const char *Config::getString(const char *key, const char *def) const {
        Iterator it = kv.find(key);

        if (kv.end() == it) {
            return def;
        } else {
            return it->second.c_str();
        }
    }

    const char *Config::getString(const std::string &key) const {
        return getString(key.c_str());
    }

    const char *Config::getString(const std::string &key,
                                  const std::string &def) const {
        return getString(key.c_str(), def.c_str());
    }

    int64_t Config::getInt64(const char *key) const {
        int64_t retval;
        Iterator it = kv.find(key);

        if (kv.end() == it) {
            THROW(HdfsConfigNotFound, "Config key: %s not found", key);
        }

        try {
            retval = StrToInt64(it->second.c_str());
        } catch (const HdfsBadNumFoumat &e) {
            NESTED_THROW(HdfsConfigNotFound, "Config key: %s not found", key);
        }

        return retval;
    }

    int64_t Config::getInt64(const char *key, int64_t def) const {
        int64_t retval;
        Iterator it = kv.find(key);

        if (kv.end() == it) {
            return def;
        }

        try {
            retval = StrToInt64(it->second.c_str());
        } catch (const HdfsBadNumFoumat &e) {
            NESTED_THROW(HdfsConfigNotFound, "Config key: %s not found", key);
        }

        return retval;
    }

    int32_t Config::getInt32(const char *key) const {
        int32_t retval;
        Iterator it = kv.find(key);

        if (kv.end() == it) {
            THROW(HdfsConfigNotFound, "Config key: %s not found", key);
        }

        try {
            retval = StrToInt32(it->second.c_str());
        } catch (const HdfsBadNumFoumat &e) {
            NESTED_THROW(HdfsConfigNotFound, "Config key: %s not found", key);
        }

        return retval;
    }

    int32_t Config::getInt32(const char *key, int32_t def) const {
        int32_t retval;
        Iterator it = kv.find(key);

        if (kv.end() == it) {
            return def;
        }

        try {
            retval = StrToInt32(it->second.c_str());
        } catch (const HdfsBadNumFoumat &e) {
            NESTED_THROW(HdfsConfigNotFound, "Config key: %s not found", key);
        }

        return retval;
    }

    double Config::getDouble(const char *key) const {
        double retval;
        Iterator it = kv.find(key);

        if (kv.end() == it) {
            THROW(HdfsConfigNotFound, "Config key: %s not found", key);
        }

        try {
            retval = StrToDouble(it->second.c_str());
        } catch (const HdfsBadNumFoumat &e) {
            NESTED_THROW(HdfsConfigNotFound, "Config key: %s not found", key);
        }

        return retval;
    }

    double Config::getDouble(const char *key, double def) const {
        double retval;
        Iterator it = kv.find(key);

        if (kv.end() == it) {
            return def;
        }

        try {
            retval = StrToDouble(it->second.c_str());
        } catch (const HdfsBadNumFoumat &e) {
            NESTED_THROW(HdfsConfigNotFound, "Config key: %s not found", key);
        }

        return retval;
    }

    bool Config::getBool(const char *key) const {
        bool retval;
        Iterator it = kv.find(key);

        if (kv.end() == it) {
            THROW(HdfsConfigNotFound, "Config key: %s not found", key);
        }

        try {
            retval = StrToBool(it->second.c_str());
        } catch (const HdfsBadBoolFoumat &e) {
            NESTED_THROW(HdfsConfigNotFound, "Config key: %s not found", key);
        }

        return retval;
    }

    bool Config::getBool(const char *key, bool def) const {
        bool retval;
        Iterator it = kv.find(key);

        if (kv.end() == it) {
            return def;
        }

        try {
            retval = StrToBool(it->second.c_str());
        } catch (const HdfsBadNumFoumat &e) {
            NESTED_THROW(HdfsConfigNotFound, "Config key: %s not found", key);
        }

        return retval;
    }

    size_t Config::hash_value() const {
        std::vector<size_t> values;
        std::map<std::string, std::string>::const_iterator s, e;
        e = kv.end();

        for (s = kv.begin(); s != e; ++s) {
            values.push_back(StringHasher(s->first));
            values.push_back(StringHasher(s->second));
        }

        return CombineHasher(values.data(), values.size());
    }
}
